// Copyright 2022 - 2023 Wenmeng See the COPYRIGHT
// file at the top-level directory of this distribution.
// 
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
// 
// Author: tickbh
// -----
// Created Date: 2023/09/01 04:38:29

use crate::{WebResult, Buf, http::http2::frame::{Kind, Flag}, Http2Error, Serialize, BufMut};

use super::{FrameHeader, Frame, StreamIdentifier};


pub type Payload = [u8; 8];

#[derive(Debug, Eq, PartialEq, Clone)]
pub struct Ping {
    ack: bool,
    payload: Payload,
}

// This was just 8 randomly generated bytes. We use something besides just
// zeroes to distinguish this specific PING from any other.
pub const SHUTDOWN_PAYLOAD: Payload = [0x0b, 0x7b, 0xa2, 0xf0, 0x8b, 0x9b, 0xfe, 0x54];
pub const USER_PAYLOAD: Payload = [0x3b, 0x7c, 0xdb, 0x7a, 0x0b, 0x87, 0x16, 0xb4];

impl Ping {

    pub const SHUTDOWN: Payload = SHUTDOWN_PAYLOAD;
    pub const USER: Payload = USER_PAYLOAD;

    pub fn new(payload: Payload) -> Ping {
        Ping {
            ack: false,
            payload,
        }
    }

    pub fn pong(payload: Payload) -> Ping {
        Ping { ack: true, payload }
    }
    
    pub fn ret_pong(&self) -> Ping {
        Ping { ack: true, payload: self.payload }
    }

    pub fn is_ack(&self) -> bool {
        self.ack
    }

    pub fn payload(&self) -> &Payload {
        &self.payload
    }

    pub fn into_payload(self) -> Payload {
        self.payload
    }

    /// Builds a `Ping` frame from a raw frame.
    pub fn parse<B: Buf>(head: FrameHeader, bytes: &mut B) -> WebResult<Ping> {
        debug_assert_eq!(head.kind(), &Kind::Ping);

        // PING frames are not associated with any individual stream. If a PING
        // frame is received with a stream identifier field value other than
        // 0x0, the recipient MUST respond with a connection error
        // (Section 5.4.1) of type PROTOCOL_ERROR.
        if !head.stream_id().is_zero() {
            return Err(Http2Error::InvalidStreamId.into());
        }

        // In addition to the frame header, PING frames MUST contain 8 octets of opaque
        // data in the payload.
        if bytes.remaining() != 8 {
            return Err(Http2Error::BadFrameSize.into());
        }

        let mut payload = [0; 8];
        bytes.copy_to_slice(&mut payload);

        // The PING frame defines the following flags:
        //
        // ACK (0x1): When set, bit 0 indicates that this PING frame is a PING
        //    response. An endpoint MUST set this flag in PING responses. An
        //    endpoint MUST NOT respond to PING frames containing this flag.
        let ack = head.flag().contains(Flag::ack());

        Ok(Ping { ack, payload })
    }

    
    pub(crate) fn head(&self) -> FrameHeader {
        let flags = if self.ack { Flag::ack() } else { Flag::zero() };
        let mut head = FrameHeader::new(Kind::Ping, flags.into(), StreamIdentifier::zero());
        head.length = self.payload.len() as u32;
        head
    }

    
    pub fn encode<B: Buf+BufMut>(&self, dst: &mut B) -> WebResult<usize> {
        let head = self.head();
        let mut size = 0;
        size += head.encode(dst)?;
        size += dst.put_slice(&self.payload);
        log::trace!("HTTP2: 编码ping信息; len={}", size);
        Ok(size)
    }

}

impl Serialize for Ping {
    fn serialize<B: Buf+BufMut>(&mut self, buffer: &mut B) -> crate::WebResult<usize> {
        let mut size = 0;
        size += self.head().encode(buffer)?;
        size += buffer.put_slice(&self.payload);
        Ok(size)
    }
}

impl<T> From<Ping> for Frame<T> {
    fn from(src: Ping) -> Frame<T> {
        Frame::Ping(src)
    }
}
